import java.awt.CardLayout;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Iterator;

import javax.swing.JTextArea;

public class ClientSession
{

	// Properties
	public static final int SERVER_PORT = 4444;
	public static final String SERVER_IP = "139.179.103.232";

	public static final int SERVER_CHAT_PORT = 5555;

	
	public static final int MEM_BUFFER_SIZE = 4096;
	
	// Local Directories
	private FileSystems sharedFolder;
	private FileSystems privateFolder;
	
	private Socket connSocket; 
	private Socket chatSocket;
	
	private DataInputStream dataIn;
	private DataOutputStream dataOut;

	private DataOutputStream chatDataOut;
	private DataInputStream chatDataIn;
	
	String uName;

	ChatClientThread _thread;
    ChatRoomPanel crp;
	private PrintWriter _out;
	private BufferedReader _in;
	
	// Constructors
	public ClientSession()
	{
		// Create Stuff
		// Initial
	}
	
	// Methods
	@SuppressWarnings("deprecation")
	public void launch() 
	{
		// Launch console thread
		
		// Init Streams
		BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
		
		// Socket and its associated streams
		try 
		{
			connSocket = new Socket(SERVER_IP, SERVER_PORT);
			chatSocket = new Socket(SERVER_IP,SERVER_CHAT_PORT);
			
			// InputStream
			dataIn = new DataInputStream(connSocket.getInputStream());
			chatDataIn = new DataInputStream(chatSocket.getInputStream());
			
			// OutputStream
			dataOut = new DataOutputStream(connSocket.getOutputStream());
			chatDataOut = new DataOutputStream(chatSocket.getOutputStream());
			

            _out = new PrintWriter(chatSocket.getOutputStream(), true);

            _in = new BufferedReader(new InputStreamReader(
            		chatSocket.getInputStream()));
		} 
		catch (IOException e) 
		{
			System.out.println("Unable to Establish Connection with server...");
			e.printStackTrace();
			return;
		}
		
		// Connection Established
		// Do Login
		System.out.println("Connection Established...");
		System.out.println("Please Enter Your Username:");
		
		try
		{
			uName = stdIn.readLine();
			dataOut.write(toASCII(uName + "\n"));
			dataOut.flush();
			
			// Wait Respond
			String respond = dataIn.readLine();
			System.out.println(respond + " " + uName + ".");
			
			if(respond.equals("Welcome"))
			{
				// Create fileSystems
				crp = new ChatRoomPanel(this);
				crp.pack();
				crp.setVisible(true);
				_thread = new ChatClientThread(this);
				_thread.start();
				sharedFolder = new FileSystems("Files/"+ uName + "/Shared");
				privateFolder = new FileSystems("Files/"+ uName + "/Private");
				
				// Initial File Check
				System.out.println("CLIENT CONNECTED");
				
				boolean running = true;
				while(running)
				{
					System.out.println("READLINE GELDIM");
					String s = stdIn.readLine();

/*					System.out.println("YOUWRITE   " + s);
*/
					//dataOut.flush();

					System.out.print("GRIDM SYNCE!");
					
					//syncPrivate();
					//syncShared();
	
					System.out.println("SYNC DEN CIKTIM");
					

					//getSharedFolderChanges();
					uploadCommand("error.dmp", "shared");


					//uploadCommand("wowcata.jpg", "shared");
					
					//dataOut.write(toASCII(s + "\n"));

//					dataOut.write(toASCII(s + "\n"));

					// Retrieve Modified File List from Server
					// Get your own modified deleted list a swell
					// Compare those if there is a conflict "server is always right"
					// If there is no conflict do the task.

				}
				
				// Do Main Loop
				connSocket.close();
			}
		}
		catch(IOException e)
		{
			System.out.println("IO Error....");
			e.printStackTrace();
		}
	}
	
	private static byte[] toASCII(String s) throws UnsupportedEncodingException
	{
		return s.getBytes("US-ASCII");
	}
	
	private void downloadCommand(String fileName, String type)
	{	
		byte memBuffer[] = new byte[MEM_BUFFER_SIZE];
		int bytesRead = 0;	
		
		FileOutputStream fileOut = null;
		File reqFile = null;
		
		// Determine folder
		String invalidatedFileName = "TMPData.tempdata";
		if (type.equals("private"))
			reqFile = new File(privateFolder.getDirectory() + "\\" + invalidatedFileName);
		else if (type.equals("shared"))
			reqFile = new File(sharedFolder.getDirectory() + "\\" + invalidatedFileName);
		
		// File Opened Fill File
		try 
		{	
			// Send Command
			dataOut.write(toASCII("download " + type  + " " + fileName + "\n"));
			dataOut.flush();
			
			// Wait for file size
			long fileSize = dataIn.readLong();

			long totalBytesRead = 0;
			fileOut = new FileOutputStream(reqFile);
			
			// File Loop
			while((bytesRead = dataIn.read(memBuffer)) >= 0)
			{
				System.out.println("BYTE READ :" + bytesRead);
				totalBytesRead += bytesRead;
				fileOut.write(memBuffer, 0, bytesRead);
				fileOut.flush();
				if(totalBytesRead >= fileSize)
					break;
			}
			fileOut.close();
					
			if(totalBytesRead < fileSize)
			{
				System.out.println("ERROR");
			}
			else if(totalBytesRead > fileSize)
			{
				System.out.println("WTF");
			}
			else
			{
				File realFile = null;
				System.out.println("Downloaded File");
				if (type.equals("private"))
					realFile = new File(privateFolder.getDirectory() + "\\" + fileName);
				else if (type.equals("shared"))
					realFile = new File(sharedFolder.getDirectory() + "\\" + fileName);
				
				// Delete Old File
				// Rename file as new
				realFile.delete();
				reqFile.renameTo(realFile);
			}
		} 
		catch (IOException e) 
		{
			System.out.println("Download Error");
			e.printStackTrace();
		}
	}

	@SuppressWarnings("deprecation")
	private boolean uploadCommand(String fileName, String type)
	{	
		// Buffer Aloc
		byte memBuffer[] = new byte[MEM_BUFFER_SIZE];
		int bytesRead = 0;
		
		// Opening File
		FileInputStream fileIn = null;
		File reqFile = null;
		if (type.equals("private"))
			reqFile = new File(privateFolder.getDirectory() + "\\" + fileName);

		else if (type.equals("shared"))
			reqFile = new File(sharedFolder.getDirectory() + "\\" + fileName);
		
		// File Opened Send File
		try
		{	
			// Send the command
			dataOut.write(toASCII("upload " + type  + " " + fileName + "\n"));
			
			// First Send The Size
			long fileSize = reqFile.length();
			dataOut.writeLong(fileSize);
			dataOut.flush();
			if( !dataIn.readLine().equals("ok") )
				return false;
			
			fileIn = new FileInputStream(reqFile);
			while((bytesRead = fileIn.read(memBuffer)) >= 0)
		    {
				System.out.println("UPLOAD EDIOM....");
		        dataOut.write(memBuffer,0,bytesRead);
		        dataOut.flush();
		    }
			// DL done
			fileIn.close();
		}
		catch(IOException e)
		{
			System.out.println("Upload Error");
			e.printStackTrace();
			return false;
		}
		return true;
	}

	@SuppressWarnings("deprecation")
	private ArrayList<String> getPrivateFolderChanges(String type)
	{
		try 
		{
			// Send Command
			dataOut.write(toASCII("getprivatelist " + type + "\n"));
			dataOut.flush();
			
			// Wait Entries
			ArrayList<String> changedFiles = new ArrayList<String>();
		
			// Get Entry Size
			int entrySize = dataIn.readInt();
			
			// Receive  lines
			for(int i = 0; i < entrySize; i++)
			{
				changedFiles.add(dataIn.readLine());
/*DEBUG*/		System.out.println(changedFiles.get(i));
			}
			// Done
			return changedFiles;
		} 
		catch (IOException e) 
		{
			System.out.println("Unable to get private folder entries..");
			e.printStackTrace();
		}
		return null;
	}
	
	@SuppressWarnings("deprecation")
	private ArrayList<String> getSharedFolderChanges(String type)
	{
		try 
		{
			// Send Command
			dataOut.write(toASCII("getsharedlist " + type +  "\n"));
			dataOut.flush();
			
			// Wait Entries
			ArrayList<String> changedFiles = new ArrayList<String>();
		
			// Get Entry Size
			int entrySize = dataIn.readInt();
			
			// Receive  lines
			for(int i = 0; i < entrySize; i++)
			{
				changedFiles.add(dataIn.readLine());
/*DEBUG*/		System.out.println(changedFiles.get(i));
			}
			// Done
			return changedFiles;
		} 
		catch (IOException e) 
		{
			System.out.println("Unable to get private folder entries..");
			e.printStackTrace();
		}
		return null;
	}

	private synchronized void syncPrivate()
	{
		ArrayList<String> serverChangesPriv = getPrivateFolderChanges("change");
		ArrayList<String> clientModified = privateFolder.changedList();
		
		System.out.println("Console");
		
		System.out.println("MODIFS");
		Iterator<String> i = serverChangesPriv.iterator();
		while(i.hasNext())
			System.out.println(i.next());
		
		// Now Iterate my Changes
		for(String s: clientModified)
		{
			if(serverChangesPriv.contains(s))
			{
				//CONFLICT FOUND
				// Server always right dl the servers data
				try 
				{
					Files.move(Paths.get(privateFolder.getDirectory() + "\\" + s + ".conflict"), 
							Paths.get(privateFolder.getDirectory() + "\\" + s),
							StandardCopyOption.ATOMIC_MOVE,
							StandardCopyOption.REPLACE_EXISTING);
				
					downloadCommand(s, "private");
					privateFolder.addModifiedtoOld(s);
				} 
				catch (IOException e) 
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
			else
			{
				if(uploadCommand(s, "private"))
				{
					privateFolder.addModifiedtoOld(s);
				}
				clientModified = privateFolder.changedList();
			}
		}
		// DL the rest
		for(String s : serverChangesPriv)
		{
			if(!clientModified.contains(s))
				downloadCommand(s, "private");
		}
		
		////////////////////////////////////////
		//
		// DELETE PART
		//
		////////////////////////////////////////
		ArrayList<String> serverDeletesPriv = getPrivateFolderChanges("delete");
		ArrayList<String> clientDeleted = privateFolder.removedList();
	
		System.out.println("DELTES");
		Iterator<String> j = serverDeletesPriv.iterator();
		while(j.hasNext())
			System.out.println(j.next());
		
		// Now Iterate my Deletes
		for(String s: clientDeleted)
		{
			if(serverDeletesPriv.contains(s))
			{
				//CONFLICT FOUND
				// Server always right dl the servers data
				try 
				{
					Files.move(Paths.get(privateFolder.getDirectory() + "\\" + s + ".conflict"), 
							Paths.get(privateFolder.getDirectory() + "\\" + s),
							StandardCopyOption.ATOMIC_MOVE,
							StandardCopyOption.REPLACE_EXISTING);
				
					downloadCommand(s, "private");
					privateFolder.addModifiedtoOld(s);
				} 
				catch (IOException e) 
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
			else
			{
				if(uploadCommand(s, "private"))
				{
					privateFolder.addModifiedtoOld(s);
				}
				clientDeleted = privateFolder.changedList();
			}
		}
		
		// DL the rest
		for(String s : serverDeletesPriv)
		{
			if(!clientDeleted.contains(s))
				downloadCommand(s, "private");
		}
		
		// ALL DONE
		try 
		{
			privateFolder.update();
		} catch (IOException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// Sync FileSystem class back it up.		
	}
	
	private synchronized void syncShared()
	{
		ArrayList<String> serverChangesShr = getSharedFolderChanges("change");
		ArrayList<String> clientModified = privateFolder.changedList();
		
		System.out.println("Console");
		
		// Now Iterate my Changes
		for(String s: clientModified)
		{
			if(serverChangesShr.contains(s))
			{
				//CONFLICT FOUND
				// Server always right dl the servers data
				try 
				{
					Files.move(Paths.get(sharedFolder.getDirectory() + "\\" + s + ".conflict"), 
							Paths.get(sharedFolder.getDirectory() + "\\" + s),
							StandardCopyOption.ATOMIC_MOVE,
							StandardCopyOption.REPLACE_EXISTING);
				
					downloadCommand(s, "shared");
					sharedFolder.addModifiedtoOld(s);
				} 
				catch (IOException e) 
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
			else
			{
				if(uploadCommand(s, "shared"))
				{
					sharedFolder.addModifiedtoOld(s);
				}
				clientModified = sharedFolder.changedList();
			}
		}
		// DL the rest
		for(String s : serverChangesShr)
		{
			if(!clientModified.contains(s))
				downloadCommand(s, "shared");
		}
		
		////////////////////////////////////////
		//
		// DELETE PART
		//
		////////////////////////////////////////
		ArrayList<String> serverDeletesShr = getSharedFolderChanges("delete");
		ArrayList<String> clientDeleted = sharedFolder.removedList();
	
		// Now Iterate my Deletes
		for(String s: clientDeleted)
		{
			if(serverDeletesShr.contains(s))
			{
				//CONFLICT FOUND
				// Server always right dl the servers data
				try 
				{
					Files.move(Paths.get(sharedFolder.getDirectory() + "\\" + s + ".conflict"), 
							Paths.get(sharedFolder.getDirectory() + "\\" + s),
							StandardCopyOption.ATOMIC_MOVE,
							StandardCopyOption.REPLACE_EXISTING);
				
					downloadCommand(s, "private");
					sharedFolder.addModifiedtoOld(s);
				} 
				catch (IOException e) 
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				
			}
			else
			{
				if(uploadCommand(s, "shared"))
				{
					sharedFolder.addModifiedtoOld(s);
				}
				clientDeleted = sharedFolder.removedList();
			}
		}
		
		// DL the rest
		for(String s : serverDeletesShr)
		{
			if(!clientDeleted.contains(s))
				downloadCommand(s, "shared");
		}
		
		// ALL DONE
		try 
		{
			sharedFolder.update();
		} catch (IOException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		// Sync FileSystem class back it up.	
		
		// Wait 2 seconds after sync.
		try 
		{
			this.wait(2000);
		} 
		catch (InterruptedException e) 
		{
			System.out.println("Interrupted......");
			e.printStackTrace();
		}
	}

	
	public synchronized void sendMessage(String fileName, String message) 
	{
		//addmessage <filename> <message>
		try 
		{
			dataOut.write(toASCII("addMessage " + fileName + " " + message +"\n"));
		}
		catch ( IOException e) 
		{
			System.out.println("UNsable to Send Message....");
			e.printStackTrace();
		}
	}

	public void sendMessage(String msg) {
		// TODO Auto-generated method stub
		try {

            msg = uName + "> " + msg;

            _out.println(msg);

        } catch (Exception e) {

            System.out.println("ChatClient err: " + e.getMessage());
            e.printStackTrace();
        }

	}

	public static void quit() {
		// TODO Auto-generated method stub
		System.exit(0);
	}

	public Socket getSocket() {
		// TODO Auto-generated method stub
		return chatSocket;
	}

	public JTextArea getOutputArea() {
		// TODO Auto-generated method stub
		return crp.getOutputArea();
	}
	
	public boolean deleteCommand(String fileName, String type)
	{
		try 
		{
			dataOut.write(toASCII("delete " + fileName + " " + type +"\n"));
			String result = dataIn.readLine();
			if(result.equals("deleteOk"))
			{
				return true;
			}
			else
				return false;
		}
		catch ( IOException e) 
		{
			System.out.println("Unable to Send Message....");
			e.printStackTrace();
			return false;
		}
	}
}
